//$Revision: 41 $
//$Date: 2012-09-12 05:49:08 +0000 (Wed, 12 Sep 2012) $
//$Author: dev@arbrebinaire.com $
/**
 * @fileOverview
 *
 * Main JavaScript file of InstaForms
 * Requires:
 * com.arbrebinaire.i18n.i18n.js
 * com.arbrebinaire.ajax.ajax.js
 * com.arbrebinaire.forms.forms.js
 * net.datatables.jquery.dataTables.min.js
 * jQuery
 * jQuery UI
 * 
 * The attribute ArbreBinaire.lang must be set to a proper locale beforehand ('en', 'fr', etc.  )
 * as the attribute ArbreBinaire.endpoint_script_url (the script with which this script communicates using AJAX)
 *
 * @author     Francois Hetu <a href="mailto:dev@arbrebinaire.com">dev@arbrebinaire.com</a>
 * @version    SVN: $$Id: main.js 41 2012-09-12 05:49:08Z dev@arbrebinaire.com $$
 */

var ArbreBinaire = ArbreBinaire || {};

//The application constants
ArbreBinaire.InstaForms = (function(mf, $){
	mf.CONST = {
		DIALOG_HEIGHT: 450,
		DIALOG_WIDTH: 400,
		MESSAGE_BOX_DELAY_MS: 6000,
		VALIDATION_FIELD_ID_PREFIX: 'instaforms-validation-type-'
	};
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);

//Utilities
ArbreBinaire.InstaForms = (function(mf, $){
	//Partial application of the i18n fonction
	var __ = function(str){
			return ArbreBinaire.i18n.__(str, ArbreBinaire.lang);
		},
		getDatatableTranslation = function(){
			return ArbreBinaire.i18n.getDatatableTranslation(ArbreBinaire.lang);
		},
		/**
		 * 
		 * Writes application messages in the proper box
		 * By default, the message is shown for MESSAGE_BOX_DELAY_MS milliseconds
		 * 
		 * @param {string} message The message to show
		 * @param {boolean} isError Is this an error message?
		 * @param {boolean} isPermanent Should the message stay in the message box?
		 */
		showMessage = function(message, isError, isPermanent){
			var messageBox = $('#instaforms-status');
			messageBox.show();
			messageBox.text(message);
			if(isError){
				messageBox.addClass('instaforms-error-message');
				messageBox.removeClass('instaforms-ok-message');
			}else{
				messageBox.removeClass('instaforms-error-message');
				messageBox.addClass('instaforms-ok-message');
			}
			if(!isPermanent){
				setTimeout(function(){
					messageBox.hide('slow');
				}, ArbreBinaire.InstaForms.CONST.MESSAGE_BOX_DELAY_MS);	
			}
	};
	
	//Exporting the public symbols
	mf.__ = __;
	mf.getDatatableTranslation = getDatatableTranslation;
	mf.showMessage = showMessage;
	
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);

/**
 * Description of the different form element types
 * The form element type are used as templating objects to create the data structure associated (using jQuery.data()) with the form element inserted in the drop zone
 * 
 * A form element is described likewise:
 * 
 * 	shorttext: {
				//Essentially a question or a section
				title: 'Question',
				//An object describing the question or section title,
				//and 2 functions getting or setting the value from or to the appropriate dialog input
				sentence: {
					value: 'The question',
					getter: SENTENCE_GETTER,
					setter: SENTENCE_SETTER
				},
				//The unique id generated by this JavaScript application and identifies this element through time and across frontend and backend
				id: '',
				//Available validation elements for this particular element type
				validation: {
					required: {
						title: 'Required', //The label next to the checkbox
						value: false, //Is it on or off
						getter: VALIDATION_REQUIRED_GETTER, //A function to get the value from the proper checkbox the dialog form
						setter: VALIDATION_REQUIRED_SETTER  //A function to set the value of the proper checkbox in the dialog form
					},
					email: {
						title: 'Email',
						value: false,
						getter: VALIDATION_REQUIRED_GETTER,
						setter: VALIDATION_REQUIRED_SETTER
					},
					//A function to construct in the DOM (within the dialog form) the list of available validation options checkboxes
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				//A function to construct in the DOM (within the dialog form) both the sentence input and the title of the dialog
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				//A function to retrieve the values of the form element data that was inserted in the drop zone
				//Only the values sent through the wire are extracted, leaving the functions of this element aside
				extract: SINGLE_FIELD_EXTRACTOR,
				//A function to set the values of the form element data that was inserted in the drop zone
				//When data is received from the server, this function is called to hydrate this form element definition properly
				set: SINGLE_FIELD_SETTER 
			}
 * 
 * When an element is first created, or when it is already created and is edited by the user, all SETTERS and CONSTRUCTORS are called to retrieve the property values
 * from the object associated (which is managed using jQuery.data()). Then, the dialog poping up is informed with this data;
 * 
 * When the dialog save button is clicked, all the data is stored back into the form element object using the GETTER functions
 * 
 * When the data of form elements needs to be sent to the server, the EXTRACTOR functions are called to retrieve the info from the said form elements
 * 
 * When data pertaining to a form element is retrieved from the server, the SETTER functions are called to hydrate the object associated with the form element
 * 
 */
ArbreBinaire.InstaForms = (function(mf, $){
	    
	var SENTENCE_FIELD_ID              = 'instaforms-question',
		ANSWERS_FIELD_ID               = '#instaforms-answers',
		ANSWERS_FIELD_CONTAINER_ID     = '#instaforms-answers-container',
		VALIDATION_FIELDS_CONTAINER_ID = '#instaforms-validation-container',
		VALIDATION_FIELDS_LIST_ID      = '#instaforms-validation-list',
		REQUIRED_FIELD_ID              = '#' + ArbreBinaire.InstaForms.CONST.VALIDATION_FIELD_ID_PREFIX + 'required',
		EMAIL_FIELD_ID                 = '#' + ArbreBinaire.InstaForms.CONST.VALIDATION_FIELD_ID_PREFIX + 'email',
		/**
		 * Sets the values of both the label and value of the sentence field of the dialog box
		 */
		TITLE_AND_SENTENCE_CONSTRUCTOR = function(){
			$('label[for="' + SENTENCE_FIELD_ID + '"]').text(this.title);
			$('#' + SENTENCE_FIELD_ID).val(this.sentence.value);
		},
		/**
		 * Retrieves the value in the proper dialog box input and sets this object's corresponding property to the value found
		 */
		SENTENCE_GETTER = function(){
			this.value = $('#' + SENTENCE_FIELD_ID).val();
		},
		/**
		 * Gets the value from this object and sets the corresponding input to it
		 */
		SENTENCE_SETTER = function(){
			$('#' + SENTENCE_FIELD_ID).val(this.value);
		},
		ANSWER_FIELD_CONSTRUCTOR = function(){
			var answers = this.value,
				answersLength = answers.length,
				container = $(ANSWERS_FIELD_CONTAINER_ID);
			//Inserts an array of answers in the proper text area with new lines
			ArbreBinaire.Forms.setTextAreaLines($(ANSWERS_FIELD_ID), answers);
			//The container is hidden by default, since only a few input types need this field
			container.show();
		},
		ANSWERS_GETTER = function(){
			this.value = ArbreBinaire.Forms.getTextAreaLines($(ANSWERS_FIELD_ID));
		},
		ANSWERS_SETTER = function(value){
			ArbreBinaire.Forms.setTextAreaLines($(ANSWERS_FIELD_ID), value);
		},
		/**
		 * Constructs in the DOM (in the dialog box) a single check box and label representing a validation option
		 * 
		 * @param {string} fieldId The field id
		 * @param {string} labelText Its label text
		 * @param {string|boolean} value The value of the validation checkbox ('true', 'false', true or false)
		 * 
		 * @return jQuery li element to insert in the ul validation list
		 */
		VALIDATION_FIELD_CONSTRUCTOR = function(fieldId, labelText, value){
			var li = $('<li>').attr('id', fieldId),
				label = $('<label>').attr('for', fieldId).text(labelText),
				checkbox = $('<input>').attr('type', 'checkbox');
				value = typeof value == 'boolean' ? value : value == 'true';
				checkbox.prop('checked', value);
			li.append(label);
			li.append(checkbox);
			return li;
		},
		/**
		 * Builds de complete list of validation elements
		 */
		VALIDATION_LIST_CONSTRUCTOR = function(){
			var validationObjects = this,
				validationFieldListEl = $(VALIDATION_FIELDS_LIST_ID),
				validationObject = null,
				validationObjectId = '',
				validationEl = null,
				key = '';
			//We first empty the dialog's validation element list
			validationFieldListEl.empty();
			
			for(key in validationObjects){
				//We access all validation elements associated, except of course the element constructor itself
				if(validationObjects.hasOwnProperty(key) && key != 'construct'){
					
					validationObject = validationObjects[key];
					validationValue = validationObject.value;
					validationObjectId = ArbreBinaire.InstaForms.CONST.VALIDATION_FIELD_ID_PREFIX + key;
					validationEl = VALIDATION_FIELD_CONSTRUCTOR(validationObjectId, validationObject.title, validationObject.value);
					validationFieldListEl.append(validationEl);
				}
			}
			$(VALIDATION_FIELDS_CONTAINER_ID).show();		
		},
		VALIDATION_FIELD_GETTER = function(fieldId, context){
			//"Context" is the validation object
			//See "required" and "VALIDATION_REQUIRED_GETTER" below for an example
			context.value = $('input', fieldId).prop('checked');
		},
		VALIDATION_FIELD_SETTER = function(fieldId, value){
			$('input', fieldId).prop('checked', value);
		}, 
		VALIDATION_REQUIRED_GETTER = function(){
			//"this" is the validation object
			//See "required" below for an example
			VALIDATION_FIELD_GETTER(REQUIRED_FIELD_ID, this);
		},
		VALIDATION_REQUIRED_SETTER = function(value){
			VALIDATION_FIELD_SETTER(REQUIRED_FIELD_ID, value);
		},
		VALIDATION_EMAIL_GETTER = function(){
			VALIDATION_FIELD_GETTER(EMAIL_FIELD_ID, this);
		},
		VALIDATION_EMAIL_SETTER = function(value){
			VALIDATION_FIELD_SETTER(EMAIL_FIELD_ID, value);
		},
		/**
		 * Extracts all properties from the validation object for transmission purposes
		 * 
		 * @param {Object} context The validation object itself for access to its properties
		 * @return {Object} The validation data object ready for transmission
		 */
		VALIDATION_DATA_EXTRACTOR = function(context){
			var validationItems = context.validation || {},
				key = '',
				validationData = {};
			for(key in validationItems){
				//We skip this very property, with key construct
				if(validationItems.hasOwnProperty(key) && key != 'construct'){
					validationData[key] = validationItems[key]['value'];
				}
			}
			return validationData;
		},
		/**
		 * When data is received from the server, this setter hydrates the data element properly
		 * 
		 * @param {Object} data The data as received from the server
		 * @param {Object} context This very validation object
		 */
		VALIDATION_DATA_SETTER = function(data, context){
			var validationItems = context.validation || {},
				key = '',
				newValidationData = data.validation;
			for(key in newValidationData){
				if(newValidationData.hasOwnProperty(key)){
					validationItems[key]['value'] = newValidationData[key];
				}
			}
		},
		SINGLE_FIELD_EXTRACTOR = function(){
			return {
				id: this.id,
				sentence: this.sentence.value,
				validation: VALIDATION_DATA_EXTRACTOR(this)
			}
		},
		SINGLE_FIELD_SETTER = function(data){
			this.id = data.id;
			this.sentence.value = data.sentence;
			VALIDATION_DATA_SETTER(data, this);
		},
		MULTI_FIELD_EXTRACTOR = function(){
			return {
				id: this.id,
				sentence: this.sentence.value,
				validation: VALIDATION_DATA_EXTRACTOR(this),
				answers: this.answers.value
			}
		},
		MULTI_FIELD_SETTER = function(data){
			this.id = data.id;
			this.sentence.value = data.sentence;
			this.answers.value = data.answers;
			VALIDATION_DATA_SETTER(data, this);
		},
		sentence = {value: '', getter: SENTENCE_GETTER, setter: SENTENCE_SETTER},
		answers = {value: [], getter: ANSWERS_GETTER, setter: ANSWERS_SETTER, construct: ANSWER_FIELD_CONSTRUCTOR},
		required = {
			title: mf.__('Required'),
			value: false,
			getter: VALIDATION_REQUIRED_GETTER,
			setter: VALIDATION_REQUIRED_SETTER
		},
		email = {
			title: mf.__('Email'),
			value: false,
			getter: VALIDATION_EMAIL_GETTER,
			setter: VALIDATION_EMAIL_SETTER
		};
	mf.formElementTypes = {
			//A section title and delimiter
			section: {
				title: mf.__('Section title'),
				sentence: sentence,
				id: '',
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: SINGLE_FIELD_EXTRACTOR,
				set: SINGLE_FIELD_SETTER
			},
			//A text input
			shorttext: {
				title: mf.__('Question'),
				sentence: sentence,
				id: '',
				validation: {
					required: required,
					email: email,
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: SINGLE_FIELD_EXTRACTOR,
				set: SINGLE_FIELD_SETTER 
			},
			//A text area
			longtext: {
				title: mf.__('Question'),
				sentence: sentence,
				id: '',
				validation: {
					required: required,
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: SINGLE_FIELD_EXTRACTOR,
				set: SINGLE_FIELD_SETTER
			},
			//A password input
			password: {
				title: mf.__('Question'),
				sentence: sentence,
				id: '',
				validation: {
					required: required,
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: SINGLE_FIELD_EXTRACTOR,
				set: SINGLE_FIELD_SETTER
			},
			//A file input
			file: {
				title: mf.__('Question'),
				sentence: sentence,
				id: '',
				validation: {
					required: required,
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: SINGLE_FIELD_EXTRACTOR,
				set: SINGLE_FIELD_SETTER
			},
			//A set of checkboxes
			multiple_inclusive: {
				title: mf.__('Question'),
				sentence: sentence,
				id: '',
				validation: {
					required: required,
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				answers: answers,
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: MULTI_FIELD_EXTRACTOR,
				set: MULTI_FIELD_SETTER
			},
			//A select
			multiple_exclusive: {
				title: mf.__('Question'),
				sentence: sentence,
				id: '',
				validation: {
					required: required,
					construct: VALIDATION_LIST_CONSTRUCTOR
				},
				answers: answers,
				construct: TITLE_AND_SENTENCE_CONSTRUCTOR,
				extract: MULTI_FIELD_EXTRACTOR,
				set: MULTI_FIELD_SETTER
			},
			
		};	
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);

/**
 * Form element data insertion and retrieval
 * Each element in the drop zone has data describing the corresponding element in the form
 * Methods in this section deal with this data
 */
ArbreBinaire.InstaForms = (function(mf, $){
	var FIELD_TYPE_DATA_NAME         = 'instaforms_field_type',
		FIELD_DATA_NAME              = 'instaforms_field_data',
		FIELD_DROPPED_DATA_NAME      = 'instaforms_field_dropped',
		/**
		 * Retrieves the element type from its data object
		 * 
		 * @param {jQuery} $fieldElement The element in the drop zone¸
		 * @return {string} The element type
		 */
		getFieldElementType = function($fieldElement){
			return $fieldElement.data(FIELD_TYPE_DATA_NAME);
		},
		/**
		 * Retrieves the id of the element to be cloned (the template) in the left box,
		 * and assign the type name to the clone to be dropped in the right box.
		 * Id convention of the template (HTML markup): instaforms-formelement-[name of the type]
		 * 
		 * @param {jQuery} $fieldElementTemplate The template element found in the DOM
		 * @param {jQuery} $fieldElement The newly created element to be dropped in the drop zone
		 */
		_setFieldElementType = function($fieldElementTemplate, $fieldElement){
			var type = $fieldElementTemplate.attr('id').split('-');
			type = type[type.length - 1];
			$fieldElement.data(FIELD_TYPE_DATA_NAME, type);
		},
		/**
		 * Sets a field element data item to a value
		 * 
		 * @param {string} value The value to be set in the data object
		 * @param {jQuery} $fieldElement This element's data will be changed
		 * @param {string} dataItemName The key of the data item to be changed
		 */
		_setFieldDataItem = function(value, $fieldElement, dataItemName){
			var data = $fieldElement.data(FIELD_DATA_NAME);
			data[dataItemName] = value;
			$fieldElement.data(FIELD_DATA_NAME, data);
		},
		/**
		 * At creation, initializes the data object of the new form element
		 * Uses the formElementTypes as a template for the data object
		 * and injects a unique ID to this element
		 * 
		 * @param {jQuery} $fieldElement This element's data will be changed
		 */
		_initializeFieldData = function($fieldElement){
			var id = 'instaforms_' + ArbreBinaire.String.sha1(ArbreBinaire.Number.rand(10000000000, 100000000000) + '' + (new Date()).getTime());
			$fieldElement.data(FIELD_DATA_NAME, $.extend(true, {}, ArbreBinaire.InstaForms.formElementTypes[getFieldElementType($fieldElement)]));
			//Setting the unique id
			//This id is permanent and is unique across time
			_setFieldDataItem(id, $fieldElement, 'id');
		},
		/**
		 * This method setd the "dropped" property of the data object to the parameter passed
		 * 
		 * At creation, the element is not dropped yet in the drop zone
		 * If "Cancel" is clicked, the "dropped" property is still false: the element will be removed
		 * Once it is duly saved, this property is set to true so it cannot be removed anymore when "Cancel is clicked"
		 * 
		 * @param {jQuery} $fieldElement This element's data will be changed
		 * @param {boolean} isDropped
		 */
		setDropped = function($fieldElement, isDropped){
			$fieldElement.data(FIELD_DROPPED_DATA_NAME, isDropped);
		},
		/**
		 * Retrieves the "dropped" property value
		 * 
		 * @param {jQuery} $fieldElement This element's data will be retrieved
		 * @return {boolean} Is it dropped permanently in the dropped zone?
		 */
		isDropped = function($fieldElement){
			return $fieldElement.data(FIELD_DROPPED_DATA_NAME);
		},
		/**
		 * Method responsable of creating and initializating of the form element to be inserted in the drop zone
		 * 
		 * @param {jQuery} $fieldElementTemplate The field element template as retrieved from the DOM (in the available element list)
		 * @return {jQuery} The element ready to be inserted in the drop zone
		 */
		createFieldElement = function($fieldElementTemplate){
			var $fieldElement = $fieldElementTemplate.clone();
			_setFieldElementType($fieldElementTemplate, $fieldElement);
			//We need to set the field type before doing this (previous line)
			//We need to know what type it is
			_initializeFieldData($fieldElement);
			//Just a flag to indicate this field element was just created and not yet dropped
			//Will be set to true on dialog save and close
			setDropped($fieldElement, false);
			return $fieldElement;
		},
		/**
		 * Retrieves the data object
		 * 
		 * @param {jQuery} $fieldElement The element of which data is retrieved
		 * @return {Object} The data object
		 */
		getFieldData = function($fieldElement){
			return $fieldElement.data(FIELD_DATA_NAME);
		},
		/**
		 * Duplication of all the source element's data into the target element
		 * Useful at drop moment to copy all properties of DnD helper into Sorted list item
		 * 
		 * @param {jQuery} $targetElement 
		 * @param {jQuery} $sourceElement All of this element's data will be copied
		 */
		copyAllData = function($targetElement, $sourceElement){
			var sourceData = $sourceElement.data(),
				targetData = $targetElement.data(),
				key = '';
			for(key in sourceData){
				if(sourceData.hasOwnProperty(key)){
					targetData[key] = sourceData[key];
					$targetElement.data(key, targetData[key]);
				}
			}
				
		};
	mf.getFieldElementType = getFieldElementType;
	mf.setDropped = setDropped;
	mf.isDropped = isDropped;
	mf.createFieldElement = createFieldElement;
	mf.getFieldData = getFieldData;
	mf.copyAllData = copyAllData;
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);


/**
 * Dialog form info retrieval, construct and set
 * 
 */
ArbreBinaire.InstaForms = (function(mf, $){
	/**
	 * Recursive function iterating through all properties of a form element data object
	 * and calling either its getter to set the element data item to the form value
	 * or its constructor to create the form according to its data content
	 * 
	 * @param {Object|string|number|boolean} el The current data element being traversed
	 * @param {string} setOrGet Switch to invoke the getter or setter of the element at hand
	 */
	var _iterate = function(el, setOrGet){
			setOrGet = typeof setOrGet == 'undefined' ? '' :	setOrGet;				
			if(typeof el == 'object'){
				for(var key in el){
					if(el.hasOwnProperty(key)){
						if(typeof el[key] == 'object'){
							_iterate(el[key], setOrGet);
							continue;
						}
						switch(key) {
							case 'getter':
								if(setOrGet == 'get'){
									el['getter']();
								}
								break;
							//Not used at the moment
							//Please see @see setDialogFormData
							/*
							case 'setter':
								if(setOrGet == 'set'){
									//el[key](el['value']);
								}
								break;
							*/
							case 'construct':
								el['construct']();
								break;
							default: 
								//no op;
						}
					}
				}
			}
		},
		/**
		 * Uses the element getters to update the element data object with the dialog form fields
		 * 
		 * @param {jQuery} $fieldElement The form element of which data will be processed
		 */
		getDialogFormData = function($fieldElement){
			_iterate(ArbreBinaire.InstaForms.getFieldData($fieldElement), 'get');
		},
		/**
		 * Uses the element setters to retrieve data from the data object and filling in the dialog form
		 * Not used, but kept if need be
		 * 
		 * @param {jQuery} $fieldElement The form element of which data will be processed
		 */
		setDialogFormData = function($fieldElement){
			_iterate(ArbreBinaire.InstaForms.getFieldData($fieldElement), 'set');
		},
		/**
		 * Uses the element constructors to build the dialog form fields
		 * and to retrieve data from the data object to fill the dialog form
		 * 
		 * @param {jQuery} $fieldElement The form element of which data will be processed
		 */
		createFormElements = function($fieldElement){
			_iterate(ArbreBinaire.InstaForms.getFieldData($fieldElement));
		};
	mf.getDialogFormData = getDialogFormData;
	mf.setDialogFormData = setDialogFormData;
	mf.createFormElements = createFormElements;	
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);

/**
 * Dialog construction and modification on the fly
 * 
 */
ArbreBinaire.InstaForms = (function(mf, $){
	
		/**
		 * Method responsable of modifying the template dialog
		 * according to the data of the form element being created/edited
		 * 
		 * @method modifyAndOpenDialog
		 * @param {jQuery} $dialog The dialog template, taken from the DOM
		 * @param {jQuery} $fieldElement The drop zone element representing the form element
		 */
		modifyAndOpenDialog = function($dialog, $fieldElement){
			var fieldset = $('#instaforms-fieldset', $dialog),
				typeDef = ArbreBinaire.InstaForms.getFieldData($fieldElement),
				validation,
				saveButtonName = mf.__('Save'),
				cancelButtonName = mf.__('Cancel'),
				buttons = {},
				/**
				 * Function called when the dialog is closed by either clicking
				 * "Cancel" or the dialog's "X" 
				 * 
				 * @param {jQuery} $d The dialog
				 */
				cancel = function($d){
					if(!ArbreBinaire.InstaForms.isDropped($fieldElement)){
						//Only if it was not dropped
						//Otherwise, the user is only sorting the list by drag and drop
						$fieldElement.remove();	
					}
					$d.dialog('close');
				};
			//We hide both the answer container and the validation container
			//since they are optional. Constructors of the elements are
			//responsable to show them if need be
			$('#instaforms-answers-container').hide();
			$('#instaforms-validation-container').hide();
			
			//Calling the iteration function to construct the dialog form
			ArbreBinaire.InstaForms.createFormElements($fieldElement);
			
			//The save button
			buttons[saveButtonName] = function(){
				var question = $('#instaforms-question').val();
				//We do not save the element if the value is empty
				if(!question) {
					alert(mf.__('Please fill in the required field'));
					return;
				}
				
				$fieldElement.find('.instaforms-question').text(question);
				ArbreBinaire.InstaForms.setDropped($fieldElement, true);
				
				//Updating the element data with the form info
				ArbreBinaire.InstaForms.getDialogFormData($fieldElement);
				
				//We save the form remotely automatically
				$('#instaforms-save').trigger('click');
				
				$(this).dialog('close');
			}
			//The cancel button
			buttons[cancelButtonName] = function(){
				cancel($dialog);
			}
			//We remove any previous event bound before binding a new one
			$dialog.unbind('dialogclose');
			$dialog.bind('dialogclose', function(event, ui){
				//If this is the X close button of the dialog, remove form element if it's brand new 
				if($(event.srcElement).hasClass('ui-icon-closethick')){
					cancel($dialog);
				}
			});
			$dialog.dialog('option', 'buttons', buttons);
			$dialog.dialog('open');
		}
	
	mf.modifyAndOpenDialog = modifyAndOpenDialog;
	return mf;
	
})(ArbreBinaire.InstaForms || {}, jQuery);




//Management of forms placeholder and properties: data extraction and building from data
/**
 * Creation of form elements in the drop zone according with the data received over the wire,
 * and Extraction of the form elements data to send to server
 * 
 */
ArbreBinaire.InstaForms = (function(mf, $){
	/**
	 * Method building the form elements in the drop zone with the data received from the server
	 * 
	 * @param {Object} data The data received from the server
	 */
	var buildFormElements = function(data){
			var i = 0,
				formElementsData = data['fields_data'],
				formProperties = data['properties'],
				length = formElementsData.length,
				fieldType = '',
				values = {},
				template = {},
				$fieldElement = null,
				fieldElementData = {},
				closeButton = null,
				editButton = null,
				titleEl = null;
				
			//Resetting the form element div
			$('#instaforms-form').empty();
			
			//Setting properties of each element to what was sent through the wire
			for(i; i < length; ++i){
				fieldType = formElementsData[i].type;
				values = formElementsData[i].values;
				//Finding the element template
				$template = $('#instaforms-formelement-' + fieldType);
				//Building the element
				$fieldElement = ArbreBinaire.InstaForms.createFieldElement($template);
				//Retrieving the element data object
				fieldElementData = ArbreBinaire.InstaForms.getFieldData($fieldElement);
				
				//Invoking the element setter.  Please see the formElementTypes above.
				fieldElementData.set(values);
				
				//Building the close and edit element and appending them to the title
				closeButton = $('#instaforms-close-button').clone().removeAttr('id').addClass('instaforms-remove-form-element');
				editButton = $('#instaforms-edit-button').clone().removeAttr('id').addClass('instaforms-edit-form-element');
				titleEl = $('.instaforms-formelement-title', $fieldElement);
				titleEl.append(closeButton);
				titleEl.append(editButton);
				
				//To prevent id collision between the template and the clone
				$fieldElement.removeAttr('id');
				
				//$fieldElement.addClass(fieldType);
				
				//The question or section title is written on the form element
				$fieldElement.find('.instaforms-question').text(fieldElementData['sentence']['value']);
				
				ArbreBinaire.InstaForms.setDropped($fieldElement, true);
				
				$('#instaforms-form').append($fieldElement);
				$('#instaforms-form').sortable('refresh');
			}
			//Filling all properties of the form in its appropriate field
			fillFormPropertiesFields(formProperties);
		},
		/**
		 * Method to fill in the form properties form section with data received over the wire
		 * 
		 * @param {Object} properties The properties of the form
		 */
		fillFormPropertiesFields = function(properties){
			ArbreBinaire.Forms.unserializeFormArray(properties, $('#instaforms-properties'));
			/*var len = properties.length,
				multiSelectFields = {},
				$field = null,
				parts = [],
				value = '',
				show = '',
				isSelected = false,
				i = 0;
			for(i; i < len; ++i){
				$field = $('#instaforms-properties [name="' + properties[i]['name'] + '"]');
				if($field.is('select') && $field.prop('multiple')){
					if(!multiSelectFields[properties[i]['name']]){
						//Initializing the field
						$field.empty();
						multiSelectFields[properties[i]['name']] = [];
					}
					show = properties[i]['value']['text'];
					value = properties[i]['value']['value'];
					isSelected = properties[i]['value']['selected'] == 'true';
					value = parts[0];
					isSelected = parts[1] == 'true';
					$field.append($(option))
					multiSelectFields[properties[i]['name']].push(value);
				}else{
					$field.val(properties[i]['value']);
				}
			}
			for(i in multiSelectFields){
				if(multiSelectFields.hasOwnProperty(i)){
					$('#instaforms-properties [name="' + i + '"]').val(multiSelectFields[i]);
				}
			}*/
		},
		/**
		 * This method is invoked when the save action is called
		 * Retrieves all the form data and formats it
		 * to be sent to the server
		 * 
		 * @return {Object} The data object ready to be sent to the server
		 */
		extractFormData = function(){
			var data = {},
				$fieldElements = $('#instaforms-form').children(),
				len = $fieldElements.length,
				i = 0, type = '',
				fieldElementData = {},
				fieldsData = [];
			//Looping though all the elements in the drop zone
			$fieldElements.each(function(index, domElement){
				var obj = {},
					$fieldElement = $(domElement);
				obj['type'] = ArbreBinaire.InstaForms.getFieldElementType($fieldElement);
				fieldElementData = ArbreBinaire.InstaForms.getFieldData($fieldElement);
				//Each field data object has a method to extract its own data.
				//Please see the formElementTypes above. 
				obj['values'] = fieldElementData['extract']();
				fieldsData.push(obj);
			});
			data['fields_data'] = fieldsData;
			//The form properties form is serialized
			//data['properties'] = $('#instaforms-properties').serializeArray();
			data['properties'] = ArbreBinaire.Forms.serializeFormArray($('#instaforms-properties'));
			return data;
		};
		mf.buildFormElements = buildFormElements;
		mf.fillFormPropertiesFields = fillFormPropertiesFields;
		mf.extractFormData = extractFormData;
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);

/**
 * Form data final processing and ajax transmission
 * 
 */
ArbreBinaire.InstaForms = (function(mf, $){
	/**
	 * Wrapper of the ArbreBinaire.Ajax.call function
	 * 
	 * @param {Object} outgoingData Data to be sent to the server
	 * @param {function} successCallback Success callback
	 * @param {function} errorCallback Error callback
	 * @param {function} completeCallback Complete callback
	 * @param {function} beforeSendCallback Before sending callback
	 */	
	var _sendData = function(outgoingData, successCallback, errorCallback, completeCallback, beforeSendCallback){
			//ArbreBinaire.endpoint_script_url is initialized in the head of the HTML file containing the form creator
			var scriptUrl = ArbreBinaire.endpoint_script_url;
			ArbreBinaire.Ajax.call(scriptUrl, outgoingData, successCallback, errorCallback, completeCallback, beforeSendCallback);
		},
		/**
		 * Prepares and sends data to endpoint
		 * 
		 * @param {Object} rawData The data retrieved from the form property form and the elements in the drop zone
		 * @param {String} messageType Can be: "save" or "load" to either save the current form, or load and existing one
		 */
		sendData = function(rawData, messageType){
			var _callWasSuccessful = function(data){
					return data.status == 'ok';
				},
				_getPayload = function(data){
					return data.payload;
				},
				/**
 				 * If the "save" action was called and was successful,
				 * this function will be called 
				 * @param {Object} data Data received from server
				 */
				_saveSuccessCallback = function(data){
					var formId = _getPayload(data)/*,
						newFormOptionPlaceholder = $('#instaforms-load-form-temp-option-new-form'),
						formTitle = ''*/;
					//If there is a temp option, we change its value, and then remove its id
					/*if(newFormOptionPlaceholder){
						newFormOptionPlaceholder.val(formId);
						newFormOptionPlaceholder.removeAttr('id');
					}*/
					$('#instaforms-load-form').val(formId);
				},
				/**
				 * If the "load" action was called and was successful,
				 * this function will be called
				 * 
				 * @param {Object} Data received from server
				 */
				_loadSuccessCallback = function(data){
					var formData = _getPayload(data);
					ArbreBinaire.InstaForms.buildFormElements(formData);
				},
				outgoingData = {
					req_type: messageType,
					payload: rawData
				},
				/**
				 * Before sending callback
				 * 
				 * @param {Object} XMLHttpRequest Per jQuery.Ajax() definition of callback
				 */
				BSC = function(XMLHttpRequest){
			    },
				/**
				 * Success callback
				 * 
				 * @param {Object} data Data received
				 * @param {Object} textStatus Per jQuery.Ajax() definition of callback
				 * @param {Object} XMLHttpRequest Per jQuery.Ajax() definition of callback
				 */
				SC = function(data, textStatus, XMLHttpRequest){
					var response = {
						callback: function(){},
						message: ''
					},
					success = _callWasSuccessful(data);
					if(success){
						switch(messageType){
							case 'save':
								response['callback'] = _saveSuccessCallback;
								response['message'] = mf.__('The form was successfully saved');
								break;
							case 'load':
								response['callback'] = _loadSuccessCallback;
								response['message'] = mf.__('The form was successfully loaded');
								break;
							default:
								break;
						}
					}else{
						response['message'] = mf.__('An error has occured. Please try again.');
					}
					//Calling the appropriate success function 
					response['callback'](data);
					//Showing the error or success message
			    	ArbreBinaire.InstaForms.showMessage(response['message'], !success);
			    },
				/**
				 * Error callback
				 * 
				 * @param {Object} XMLHttpRequest Per jQuery.Ajax() definition of callback
				 * @param {Object} textStatus Per jQuery.Ajax() definition of callback
				 * @param {Object} errorThrown Per jQuery.Ajax() definition of callback
				 */
				EC = function(XMLHttpRequest, textStatus, errorThrown){
			        ArbreBinaire.InstaForms.showMessage(mf.__('An error has occured. Please try again.'), true);
			    },
				/**
				 * Complete callback
				 * 
				 * @param {Object} XMLHttpRequest Per jQuery.Ajax() definition of callback
				 * @param {Object} textStatus Per jQuery.Ajax() definition of callback
				 */
				CC = function(XMLHttpRequest, textStatus){
				};
			
			//Sending the data
			_sendData(outgoingData, SC, EC, CC, BSC);
		};
	mf.sendData = sendData;
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);



/**
 * Application initialization
 * Only one public method: init()
 */
ArbreBinaire.InstaForms = (function(mf, $){
	var formElementTypes = ArbreBinaire.InstaForms.formElementTypes,
		/**
		 * Initialization of jQuery Drag and Drop
		 */
		_initDAndD = function(){
			var formContainerId = 'instaforms-form',
				formContainer = $('#' + formContainerId),
				formContainerHeight = formContainer.height();
			$('.instaforms-formelements').draggable({
				connectToSortable: '#instaforms-form',
                helper: function(){
					return ArbreBinaire.InstaForms.createFieldElement($(this));
				},
                revert: 'invalid'
			});
			formContainer.sortable({
				revert: true,
                placeholder: 'ui-state-highlight',
				start: function(e, ui){
			        ui.placeholder.height(ui.item.height());
			    },
				sort: function(event, ui){
					var tolerance = 20,
						distanceFromTop = 0,
						distanceFromBottom = 0,
						recevingElTop = 0;
						
					if(ui.helper.offsetParent().is('body')){
						//The parent of this sortable is BODY
						recevingElTop = formContainer.offset().top;
						distanceFromBottom = formContainerHeight - ui.position.top + recevingElTop;
						distanceFromTop = ui.position.top - recevingElTop;
					}else{
						//The parent of this sortable is the receiving DOM el
						distanceFromBottom = formContainerHeight - ui.position.top;
						distanceFromTop = ui.position.top;
					}
					
					if(distanceFromBottom <= tolerance){
						//If the element is near the bottom of area, slider down
						formContainer.scrollTop(formContainerHeight);
					}else if(distanceFromTop <= tolerance){
						//If the element is near the top of area, slider up
						formContainer.scrollTop(0);
					}
				},
				update: function(event, ui){
					//When the element is already in the list of elements and is
					//put in a different position, save the new state on the server
					if(ArbreBinaire.InstaForms.isDropped(ui.item)){
						mf.sendData(ArbreBinaire.InstaForms.extractFormData(), 'save');
					}
				},
                tolerance: 'pointer',
				beforeStop: function(event, ui){
					var closeButton,
						editButton,
						titleEl;
					if(!ArbreBinaire.InstaForms.isDropped(ui.item)){
						//This section code is only performed when an element of the left section
						//is dragged in the drop zone
						//We initialize a new element for the drop zone, which will be set to "dropped" only if saved
						closeButton = $('#instaforms-close-button').clone().removeAttr('id').addClass('instaforms-remove-form-element');
						editButton = $('#instaforms-edit-button').clone().removeAttr('id').addClass('instaforms-edit-form-element');
						titleEl = $('.instaforms-formelement-title', ui.item);
						ArbreBinaire.InstaForms.copyAllData(ui.item, ui.helper);
						//ui.item.addClass(ArbreBinaire.InstaForms.getFieldElementType(ui.helper));
						titleEl.append(closeButton);
						titleEl.append(editButton);
						mf.modifyAndOpenDialog($('#instaforms-dialog-template'), ui.item);
					}else{
						//Nothing to do
					}
				}
			});
		},
		/**
		 * Initialization of the dialog with the proper div in the DOM
		 * It's a modal dialog
		 */
		_initDialog = function(){
			var buttons = {};
			//Buttons will actually be set when a particular element is created/modified
			buttons[mf.__('Save')] = function(){
				$(this).dialog('close');
			};
			buttons[mf.__('Cancel')] = function(){
				$(this).dialog('close');
			};
			$('#instaforms-dialog-template').dialog({
				autoOpen: false,
				height: ArbreBinaire.InstaForms.CONST.DIALOG_HEIGHT,
				width: ArbreBinaire.InstaForms.CONST.DIALOG_WIDTH,
				modal: true,
				buttons: buttons,
				close: function() {
				}
			});
		},
		/**
		 * Initialization of the jQuery date pickers (2 of them)
		 */
		_initCalendars = function(){
			$.datepicker.regional[ArbreBinaire.lang];
			$('.instaforms-date').datepicker({
				regional: ArbreBinaire.lang,
				dateFormat: 'yy-mm-dd'
			});
		},
		/**
		 * Any button with class "instaform-button" found on the page will become a jQuery button 
		 */
		_initButtons = function(){
			$('button.instaform-button').button();
		},
		/**
		 * Event handlers initialization
		 */
		_initEventHandlers = function(){
			//Form element remove button event handler
			$('.instaforms-remove-form-element').live('click', function(){
				$(this).closest('.instaforms-formelements').remove();
				ArbreBinaire.InstaForms.sendData(ArbreBinaire.InstaForms.extractFormData(), 'save');
			});
			//Form element edit button event handler
			$('.instaforms-edit-form-element').live('click', function(){
				mf.modifyAndOpenDialog($('#instaforms-dialog-template'), $(this).closest('.instaforms-formelements'));
			});
			//Save action
			$('#instaforms-save').click(function(){
				var formTitle = ArbreBinaire.String.trim($('#instaforms-form-title').val()),
					formId = $('#instaforms-load-form').val(),
					$selectedOption = null,
					$option;
				//3 possibilities:
				//1. Empty title field and no id : we have a brand new form: ask for a title, set title, create option, set option to new
				//2. Something in the title field and no id: brand new form with a title: set title field, create option, set option to new
				//3. Empty title field and an id : we ask for a title: set title field, set option text
				if(!formTitle){
					formTitle = ArbreBinaire.String.trim(prompt(mf.__('Please give your form a title') + ':'));
					if(!formTitle){
						//We just quit
						return false;
					}
					$('#instaforms-form-title').val(formTitle);
				}
				//State: we have a title in the title box and a properly initialized variable
				if(!formId){
					//We need to create a new option
					$option = $('<option>').val('new');
					$option.prop('selected', true).attr('id', 'instaforms-load-form-temp-option-new-form');
					$('#instaforms-load-form').prepend($option);
				}
				$selectedOption = $('#instaforms-load-form option:selected');
				//State: we have an option, with no text, wrong text or proper text
				$selectedOption.text(formTitle);
				//We are now ready to submit the form
				ArbreBinaire.InstaForms.sendData(ArbreBinaire.InstaForms.extractFormData(), 'save');
				return false;
			});
			//Load action
			/*$('#instaforms-load-form').change(function(){
				var control = $(this),
					formId = control.val();
				if(formId){
					//console.log('Calling LOAD');
					ArbreBinaire.InstaForms.sendData({form_id: formId}, 'load');
				}else{
					ArbreBinaire.InstaForms.showMessage(mf.__('Please select a form to load'), true);
				}
				return false;
			});*/
			
		},
		/**
		 * If found on the page, initializes the jQuery Datatables (datatables.net) showing the answers of a form
		 */
		_initDatatable = function(){
			$('#instaforms-datatable-answers').dataTable({
			   'bJQueryUI': true,
			   'oLanguage': ArbreBinaire.InstaForms.getDatatableTranslation()
			 });
		},
		/**
		 * Initializes the whole application
		 */
		init = function(){
			_initDAndD();
			_initDialog();
			_initCalendars();
			_initButtons();
			_initEventHandlers();
			_initDatatable();
			
			//We initialized the form if it is not "new"
			var formId = $('#instaforms-load-form').val();
			if(formId && formId != 'new'){
				ArbreBinaire.InstaForms.sendData({form_id: formId}, 'load');
			}
		};
	mf.init = init;
	return mf;
})(ArbreBinaire.InstaForms || {}, jQuery);

//Load when ready
jQuery(document).ready(ArbreBinaire.InstaForms.init);